package com.runewbie.linkedlist.circlesinglelinkedlist;

/**
 * @description:  Josephu(约瑟夫、约瑟夫环、丢手绢)  问题
 * Josephu  问题为：设编号为1，2，… n的n个人围坐一圈，约定编号为k（1<=k<=n）的人从1开始报数，数到m 的那个人出列，
 * 			它的下一位又从1开始报数，数到m的那个人又出列，依次类推，直到所有人出列为止，由此产生一个出队编号的序列。
 * @author: runewbie
 * @date:   2020年6月9日 下午11:46:55
 * @version V1.0
 * 用一个不带头结点的循环链表来处理Josephu 问题：先构成一个有n个结点的单循环链表，然后由k结点起从1开始计数，计到m时，
 * 对应结点从链表中删除，然后再从被删除结点的下一个结点又从1开始计数，直到最后一个结点从链表中删除算法结束。
 */
public class Josepfu {

	public static void main(String[] args) {
		//测试，构建一个环形链表
		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
		circleSingleLinkedList.add(5);
		//显示
		circleSingleLinkedList.showBoy();
		
		//测试
		circleSingleLinkedList.countBoy(1, 2, 5);
	}

}

//创建一个不带头节点的环形的单向链表
class CircleSingleLinkedList {
	// 创建一个first节点，当前没有编号，first是指向当前链表创建时的的第一个节点的，不会变化
	private Boy first = null;

	// 添加小孩节点，构成一个n个节点的环形链表
	public void add(int n) {
		// 对n进行一个数据校验
		if (n < 1) {
			System.out.println("n的值不正确");
			return;
		}
		Boy curBoy = null;// 辅助指针，用来指向当前节点，用来帮助构建环形链表
		// 使用for来创建环形链表
		for (int i = 1; i <= n; i++) {
			// 根据编号，创建小孩节点
			Boy boy = new Boy(i);
			// 如果是第一个小孩（节点），则next节点指向他自己
			if (i == 1) {
				first = boy;
				first.setNext(first);// next指针指向它自己，构成一个环
				curBoy = first;// curBoy指向当前节点
			} else {
				curBoy.setNext(boy);// 当前节点指向新加进来的节点
				boy.setNext(first);// 新节点指向first节点，构成环
				curBoy = boy;// curBoy辅助指针后移到当前新加进来的节点
			}
		}
	}

	// 遍历当前环形链表
	public void showBoy() {
		// 判断当前链表是否为空
		if (first == null) {
			System.out.println("当前环形链表为空，没有任何小孩");
			return;
		}
		// 因为first指向第一个节点，不能动，因此需要使用一个辅助指针来完成遍历
		Boy curBoy = first;
		while (true) {
			System.out.printf("当前链表节点（小孩）的编号：%d\n", curBoy.getNo());
			if (curBoy.getNext() == first) {
				break;
			}
			curBoy = curBoy.getNext();// 辅助指针后移到下一个节点
		}
	}
	
	/**
	 * 根据用户输入，计算出节点（小孩）出链表的顺序
	 * @param startNo 表示从第几个节点开始数
	 * @param countNum 表示数几下
	 * @param nums 表示最初链表中有几个节点（小孩）
	 */
	public void countBoy(int startNo,int countNum,int nums) {
		// 先对数据进行校验
		if(first==null || startNo<1 || startNo>nums || countNum < 1) {
			System.out.println("参数输入有误， 请重新输入");
			return;
		}
		//创建一个辅助指针，帮助完成数到的小孩出圈，也就是节点从环形链表中出来
		Boy helper = first;
		//需要创建一个辅助指针（变量）helper，事先应该指向环形链表的最后一个节点
		while(true) {
			//说明helper指向最后一个节点
			if (helper.getNext()==first) {
				break;
			}
			helper = helper.getNext();
		}
		//根据报数startNo，先让first和helper移动stratNo-1次
		for (int i = 0; i < startNo-1; i++) {
			first=first.getNext();
			helper=helper.getNext();
		}
		
		//当小孩报数时，让first和helper指针同时移动countNum-1次，然后出圈，即节点从链表中取出
		//这里是一个循环操作，直到环形链表中只有一个节点，即组成的圈中只有一个小孩
		while (true) {
			if (helper==first) {//表示链表只剩一个节点，即圈中只有一个节点
				break;
			}
			for (int i = 0; i < countNum-1; i++) {
				first=first.getNext();
				helper=helper.getNext();
			}
			//此时first指向的节点，就是要出圈的小孩的节点
			System.out.printf("小孩%d出圈\n",first.getNo());
			//将first指向的小孩节点出圈
			first=first.getNext();
			helper.setNext(first);
		}
		System.out.printf("最后留在圈中的小孩编号是%d\n",first.getNo());
	}
}

//创建一个Boy类，表示一个节点
class Boy {
	private int no;//编号
	private Boy next;//指向下一个节点，默认是null

	public Boy(int no) {
		this.no = no;
	}

	public int getNo() {
		return no;
	}

	public void setNo(int no) {
		this.no = no;
	}

	public Boy getNext() {
		return next;
	}

	public void setNext(Boy next) {
		this.next = next;
	}
}
